package com.freeter.modules.controller;


import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.freeter.annotation.Login;
import com.freeter.annotation.LoginUser;
import com.freeter.common.utils.JwtUtils;
import com.freeter.common.utils.R;
import com.freeter.common.utils.RedisUtils;
import com.freeter.modules.gjs.entity.User;
import com.freeter.modules.gjs.service.UserService;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Controller;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/auth/ms-365-auth")
public class LoginController {
	@Value("${ms365.clientId:}")
	private String clientId;
	@Value("${ms365.tenantId:common}")
	private String tenantId;
	@Value("${ms365.clientSecret:}")
	private String clientSecret;
	@Value("${ms365.redirectUri:}")
	private String redirectUri; // 必须与 Azure 应用中回调地址完全一致（绝对地址）
	@Value("${ms365.authorizeScopes:openid profile email offline_access User.Read}")
	private String authorizeScopes;
	@Value("${ms365.frontendSuccessRedirect:/}")
	private String frontendSuccessRedirect;
	@Value("${ms365.frontendFailureRedirect:/ms-365-auth-error}")
	private String frontendFailureRedirect;
	@Value("${ms365.frontendBaseUrl:}")
	private String frontendBaseUrl;

	@Autowired
	private JwtUtils jwtUtils;
	@Autowired
	private RedisUtils redisUtils;
	@Autowired
	private UserService userService;

	@GetMapping("/authorize")
	public void authorize(@RequestParam(value = "redirect", required = false) String frontendRedirect,
								  HttpServletResponse response) throws IOException {
		// 生成并封装 state（可携带前端想去的页面）
		String statePayload = buildState(frontendRedirect);
		String encodedState = urlEncode(statePayload);

		String authorizeUrl = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/authorize"
				+ "?client_id=" + urlEncode(clientId)
				+ "&response_type=code"
				+ "&redirect_uri=" + urlEncode(redirectUri)
				+ "&response_mode=form_post"
				+ "&scope=" + urlEncode(normalizeScopes(authorizeScopes))
				+ "&prompt=login"
				+ "&state=" + encodedState
				+ "&client_info=1";
		response.setStatus(HttpServletResponse.SC_FOUND);
		response.setHeader("Location", authorizeUrl);
		//return new RedirectView(authorizeUrl);
	}

	@PostMapping("/callback")
	public void callback(
			@RequestParam Map<String, String> formParams,
			HttpServletRequest httpRequest,
			HttpServletResponse httpResponse
	) throws IOException {
		String error = formParams.get("error");
		String errorDescription = formParams.get("error_description");
		String code = formParams.get("code");
		String state = formParams.get("state");

		if (error != null && !error.isEmpty()) {
			String target = buildFrontendRedirect(state, true, "error=" + urlEncode(error) + "&error_description=" + urlEncode(nullToEmpty(errorDescription)));
			httpResponse.setStatus(HttpServletResponse.SC_FOUND);
			httpResponse.setHeader("Location", target);
			return;
		}

		if (code == null || code.isEmpty()) {
			String target = buildFrontendRedirect(state, true, "error=invalid_request&error_description=missing_code");
			httpResponse.setStatus(HttpServletResponse.SC_FOUND);
			httpResponse.setHeader("Location", target);
			return;
		}

		// 使用授权码换取令牌
		try {
			RestTemplate restTemplate = new RestTemplate();

			String tokenEndpoint = "https://login.microsoftonline.com/" + tenantId + "/oauth2/v2.0/token";
			HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

			MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
			body.add("client_id", clientId);
			body.add("scope", normalizeScopes(authorizeScopes));
			body.add("code", code);
			body.add("redirect_uri", redirectUri);
			body.add("grant_type", "authorization_code");
			if (clientSecret != null && !clientSecret.isEmpty()) {
				body.add("client_secret", clientSecret);
			}

			HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
			ResponseEntity<Map> tokenResp = restTemplate.exchange(tokenEndpoint, HttpMethod.POST, request, Map.class);

			if (tokenResp.getStatusCode().is2xxSuccessful() && tokenResp.getBody() != null) {
				Map<String, Object> tokenBody = tokenResp.getBody();
				Object accessToken = tokenBody.get("access_token");
				if (accessToken == null) {
					String target = buildFrontendRedirect(state, true, "error=token_no_access_token");
					httpResponse.setStatus(HttpServletResponse.SC_FOUND);
					httpResponse.setHeader("Location", target);
					return;
				}

				// 调用 Graph 获取用户信息
				HttpHeaders gh = new HttpHeaders();
				gh.set("Authorization", "Bearer " + accessToken.toString());
				HttpEntity<Void> gr = new HttpEntity<>(gh);
				ResponseEntity<Map> meResp = restTemplate.exchange("https://graph.microsoft.com/v1.0/me", HttpMethod.GET, gr, Map.class);
				if (!meResp.getStatusCode().is2xxSuccessful() || meResp.getBody() == null) {
					String target = buildFrontendRedirect(state, true, "error=graph_user_fetch_failed");
					httpResponse.setStatus(HttpServletResponse.SC_FOUND);
					httpResponse.setHeader("Location", target);
					return;
				}
				Map me = meResp.getBody();
				String email = stringOrNull(me.get("mail"));
				String userPrincipalName = stringOrNull(me.get("userPrincipalName"));
				String displayName = stringOrNull(me.get("displayName"));
				String loginName = email != null && !email.isEmpty() ? email : userPrincipalName;

				if (loginName == null || loginName.isEmpty()) {
					String target = buildFrontendRedirect(state, true, "error=graph_missing_upn");
					httpResponse.setStatus(HttpServletResponse.SC_FOUND);
					httpResponse.setHeader("Location", target);
					return;
				}

				// 关联系统用户（按用户名或邮箱匹配）
				EntityWrapper<User> wrapper = new EntityWrapper<>();
				wrapper.eq("login", loginName);
				wrapper.eq("is_deleted",0);
				wrapper.eq("enabled",1);
				User user = userService.selectOne(wrapper);
				if (user == null) {
					String target = buildFrontendRedirect(state, true, "error=user_not_found");
					httpResponse.setStatus(HttpServletResponse.SC_FOUND);
					httpResponse.setHeader("Location", target);
					return;
				}
				if (user.getEnabled() != null && user.getEnabled().intValue() != 1) {
					String target = buildFrontendRedirect(state, true, "error=user_disabled");
					httpResponse.setStatus(HttpServletResponse.SC_FOUND);
					httpResponse.setHeader("Location", target);
					return;
				}

				user.setPassword(null);
				String jwt = jwtUtils.generateToken(JSONObject.toJSONString(user));
				// 缓存登录态，与现有拦截器逻辑一致
				redisUtils.set(user.getId()+"_loginToken", jwt);
				redisUtils.set(jwt+"_loginToken_exp", cn.hutool.core.date.DateUtil.format(jwtUtils.getExpDate(), cn.hutool.core.date.DatePattern.PURE_DATETIME_MS_FORMAT));

				// Set-Cookie 基础设置
				Cookie cookie = new Cookie(jwtUtils.getHeader(), jwt);
				cookie.setHttpOnly(true);
				cookie.setPath("/");
				httpResponse.addCookie(cookie);
				// 追加 SameSite 与 Secure：HTTPS 下使用 SameSite=None; Secure，HTTP 下使用 Lax
				boolean isHttps = httpRequest != null && httpRequest.isSecure();
				String sameSiteAttr = isHttps ? "SameSite=None; Secure" : "SameSite=Lax";
				httpResponse.addHeader("Set-Cookie",
					jwtUtils.getHeader() + "=" + jwt + "; Path=/; HttpOnly; " + sameSiteAttr);

				String target = buildFrontendRedirect(state, false, null);
				httpResponse.setStatus(HttpServletResponse.SC_FOUND);
				httpResponse.setHeader("Location", target);
				return;
			}

			String target = buildFrontendRedirect(state, true, "error=token_exchange_failed");
			httpResponse.setStatus(HttpServletResponse.SC_FOUND);
			httpResponse.setHeader("Location", target);
		} catch (Exception ex) {
			String target = buildFrontendRedirect(state, true, "error=exception&error_description=" + urlEncode(ex.getMessage()));
			httpResponse.setStatus(HttpServletResponse.SC_FOUND);
			httpResponse.setHeader("Location", target);
		}
	}

//	@PostMapping("logout")
//	@Login
//	@ApiOperation("退出")
//	public R logout(HttpServletRequest request, @LoginUser UserFrontEntity loginUser){
//
//		//获取用户凭证
//		String token = request.getHeader(jwtUtils.getHeader());
//		if(StringUtils.isBlank(token)){
//			token = request.getParameter(jwtUtils.getHeader());
//		}
//		//退出
//		userService.logout(loginUser.getUserId());
//
//		//根据token记录退出时间
//		String md5LoginToken = DigestUtils.md5Hex(token);
//		return R.ok();
//	}

	private String buildFrontendRedirect(String state, boolean isError, String extraQuery) {
		String fallback = isError ? frontendFailureRedirect : frontendSuccessRedirect;
		String redirectFromState = parseRedirectFromState(state);
		String base =  fallback;
		if (extraQuery != null && ! extraQuery.isEmpty()) {
			base = base + (base.contains("?") ? "&" : "?") + extraQuery;
		}
		if(StringUtils.isNotBlank(redirectFromState)){
			base = base + (base.contains("?") ? "&" : "?") + "redirect="+redirectFromState;
		}
		return base ;
	}

	private String buildState(String frontendRedirect) {
		try {
			Map<String, String> payload = new HashMap<>();
			if (frontendRedirect != null && !frontendRedirect.isEmpty()) {
				payload.put("redirect", frontendRedirect);
			}
			String json = "{" + (payload.containsKey("redirect") ? "\"redirect\":\"" + escapeJson(payload.get("redirect")) + "\"" : "") + "}";
			return Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8));
		} catch (Exception e) {
			return "";
		}
	}

	private String parseRedirectFromState(String state) {
		try {
			if (state == null || state.isEmpty()) return null;
			byte[] decoded = Base64.getUrlDecoder().decode(state);
			String json = new String(decoded, StandardCharsets.UTF_8);
			int idx = json.indexOf("\"redirect\":\"");
			if (idx >= 0) {
				int start = idx + "\"redirect\":\"".length();
				int end = json.indexOf("\"", start);
				if (end > start) {
					String redirectUrl = json.substring(start, end);
					// 判断是否为完整的HTTP地址，如果不是则加上前端基础URL
					if (redirectUrl != null && !redirectUrl.isEmpty() && 
						!redirectUrl.startsWith("http://") && !redirectUrl.startsWith("https://")) {
						// 确保redirectUrl以/开头
						if (!redirectUrl.startsWith("/")) {
							redirectUrl = "/" + redirectUrl;
						}
						// 拼接前端基础URL
						if (frontendBaseUrl != null && !frontendBaseUrl.isEmpty()) {
							redirectUrl = frontendBaseUrl + redirectUrl;
						}
					}
					return redirectUrl;
				}
			}
		} catch (Exception ignored) {}
		return null;
	}

	private String urlEncode(String v) throws UnsupportedEncodingException {
		return v == null ? "" : URLEncoder.encode(v, StandardCharsets.UTF_8.toString());
	}

	private String nullToEmpty(String v) {
		return v == null ? "" : v;
	}

	private String escapeJson(String v) {
		return v == null ? "" : v.replace("\\", "\\\\").replace("\"", "\\\"");
	}

	private String normalizeScopes(String scopes) {
		if (scopes == null || scopes.trim().isEmpty()) return "openid profile email offline_access";
		return scopes.trim().replace(",", " ").replaceAll("\\s+", " ").trim();
	}

	private String stringOrNull(Object v) {
		return v == null ? null : String.valueOf(v);
	}
}
